use base64::{engine::general_purpose, Engine};
use image::DynamicImage;
use reqwest::{self, Client};
use std::{fs, path::Path};

use crate::io::{error::RiboIoError, json::robust_parse_json};

pub use image;

#[derive(Debug, Clone)]
pub struct ResizeInfo {
    pub original_width: u32,
    pub original_height: u32,
    pub resized_width: u32,
    pub resized_height: u32,
    pub scale: f32, // new = old * scale
}

impl ResizeInfo {
    pub fn rescale_coords_in_args_str(&self, args_str: &str) -> Result<String, RiboIoError> {
        // Rescale coordinates in args_str based on the resize info
        let mut args: serde_json::Value = serde_json::from_str(args_str)
            .map_err(|e| RiboIoError::InvalidInput(format!("Failed to parse args: {:?}", e)))?;

        if let Some(coord) = args.get_mut("coordinate") {
            if let Some(arr) = coord.as_array_mut() {
                for val in arr.iter_mut() {
                    if let Some(num) = val.as_f64() {
                        let scaled = (num * self.scale as f64).round() as u32;
                        *val = serde_json::json!(scaled);
                    }
                }
            }
        }

        serde_json::to_string(&args)
            .map_err(|e| RiboIoError::InvalidInput(format!("Failed to serialize args: {:?}", e)))
    }

    pub fn remap_qwen3vl_rescale_coords_args_str(
        &self,
        args_str: &str,
    ) -> Result<String, RiboIoError> {
        // Step 1: Parse args safely using robust_parse_json
        let mut args_json: serde_json::Value = robust_parse_json(args_str)
            .map_err(|e| RiboIoError::InvalidInput(format!("Failed to parse args JSON: {}", e)))?;

        // Step 2: Extract coordinate array
        let coord_arr = args_json["coordinate"]
            .as_array()
            .ok_or_else(|| RiboIoError::InvalidInput("coordinate is not an array".to_string()))?;

        // Step 3: Convert JSON values → u32
        let coords: Vec<u32> = coord_arr
            .iter()
            .map(|v| v.as_u64().unwrap_or(0) as u32)
            .collect();

        // Step 4: First remap Qwen3VL normalized (0–1000) coords → original image pixels
        if coords.len() == 2 {
            let (x_norm, y_norm) = (coords[0] as f32, coords[1] as f32);
            // let width = self.original_width as f32;
            // let height = self.original_height as f32;

            // Qwen3VL → pixel space
            let x_px = ((x_norm / 1000.0) * (self.resized_width as f32 - 1.0)).round() as f32;
            let y_px = ((y_norm / 1000.0) * (self.resized_height as f32 - 1.0)).round() as f32;

            // Step 5: Then apply scaling factor
            let x_scaled = (x_px / self.scale as f32).round() as u32;
            let y_scaled = (y_px / self.scale as f32).round() as u32;

            // Update JSON
            args_json["coordinate"] = serde_json::json!([x_scaled, y_scaled]);
        }

        // Step 6: Serialize back to string
        serde_json::to_string(&args_json)
            .map_err(|e| RiboIoError::InvalidInput(format!("Failed to serialize args: {}", e)))
    }
}

pub async fn image_to_base64(input: &str) -> Result<String, RiboIoError> {
    let bytes = if input.starts_with("data:image/") {
        return Ok(input.to_string());
    } else if input.starts_with("http://") || input.starts_with("https://") {
        let resp = reqwest::get(input)
            .await
            .map_err(|e| RiboIoError::DownloadError(e.to_string()))?;
        resp.bytes()
            .await
            .map_err(|e| RiboIoError::DownloadError(e.to_string()))?
            .to_vec()
    } else {
        fs::read(input)? // auto converts io::Error to RiboIoError
    };

    Ok(format!(
        "data:image/png;base64,{}",
        general_purpose::STANDARD.encode(bytes)
    ))
}

pub async fn load_any_to_image(input: &str) -> Result<DynamicImage, RiboIoError> {
    // 1. URL
    if input.starts_with("http://") || input.starts_with("https://") {
        let bytes = Client::new()
            .get(input)
            .send()
            .await
            .map_err(|e| RiboIoError::UrlError(e.to_string()))?
            .bytes()
            .await
            .map_err(|e| RiboIoError::UrlError(e.to_string()))?;

        return image::load_from_memory(&bytes)
            .map_err(|e| RiboIoError::DecodeError(e.to_string()));
    }

    if input.starts_with("data:image/")
        || input.chars().all(|c| c.is_ascii() && !c.is_control()) && input.len() > 200
    {
        let b64 = input.split(',').last().unwrap_or(input);
        let decoded = base64::engine::general_purpose::STANDARD
            .decode(b64)
            .map_err(|e| RiboIoError::DecodeError(e.to_string()))?;

        return image::load_from_memory(&decoded)
            .map_err(|e| RiboIoError::DecodeError(e.to_string()));
    }

    if Path::new(input).exists() {
        return image::open(input).map_err(|e| RiboIoError::DecodeError(e.to_string()));
    }

    Err(RiboIoError::InvalidInput(format!(
        "Invalid image input: {}",
        input
    )))
}
